use crate::address::{CARGO1, CARGO2, DOOR, HEAT, IPC, WEIGHT};
use crate::driver::cmd_can;
use core::ptr::copy_nonoverlapping;
use core::slice;
use proto_bytes::message::MessageRef;
use proto_bytes::proto::is_checked;
use utils::{log, ufmt, ByteBuffer, MutCell};

use super::dispath;

struct RecvBuffer<const SIZE: usize, const SRC: u8> {
    buf: ByteBuffer<SIZE>,
    recv_index: usize,
    dst: u8,
}

#[derive(ufmt::derive::uDebug)]
enum Error {
    Dst,
    Size,
    Checked,
}

impl<const SIZE: usize, const SRC: u8> RecvBuffer<SIZE, SRC> {
    const fn const_new() -> MutCell<Self> {
        MutCell::const_new(Self {
            buf: ByteBuffer::const_new(),
            recv_index: 0,
            dst: 0,
        })
    }

    fn put<'a>(&'a mut self, frame: cmd_can::Frame) -> Result<Option<&'a [u8]>, Error> {
        let seq = frame.pkg_seq();
        let mut index = self.recv_index;

        if seq == 0 {
            self.dst = frame.dst();
            self.recv_index = 0;
            index = 0;
        }

        let dst = frame.dst();
        if dst != self.dst {
            return Err(Error::Dst);
        }

        let data = frame.data();
        if (index + data.len()) > SIZE {
            return Err(Error::Size);
        }

        unsafe {
            copy_nonoverlapping(data.as_ptr(), self.buf.as_mut_ptr().add(index), data.len());
        }
        self.recv_index = index + data.len();

        if !frame.is_complete() {
            return Ok(None);
        }
        let buf = unsafe { slice::from_raw_parts(self.buf.as_ptr(), self.recv_index) };

        if !is_checked(buf) {
            return Err(Error::Checked);
        }

        Ok(Some(buf))
    }
}

const RECV_BUF_SIZE: usize = 255;
type MutBuf<const SRC: u8> = MutCell<RecvBuffer<RECV_BUF_SIZE, SRC>>;

static CARGO1_RECV_BUF: MutBuf<CARGO1> = RecvBuffer::const_new();
static CARGO2_RECV_BUF: MutBuf<CARGO2> = RecvBuffer::const_new();
static WEIGHT_RECV_BUF: MutBuf<WEIGHT> = RecvBuffer::const_new();
static HEAT_RECV_BUF: MutBuf<HEAT> = RecvBuffer::const_new();
static DOOR_RECV_BUF: MutBuf<DOOR> = RecvBuffer::const_new();
static IPC_RECV_BUF: MutBuf<IPC> = RecvBuffer::const_new();

pub fn recv_loop() {
    loop {
        let frame = cmd_can::take();
        let src = frame.src();

        let ret = match src {
            CARGO1 => unsafe { CARGO1_RECV_BUF.as_mut().put(frame) },
            CARGO2 => unsafe { CARGO2_RECV_BUF.as_mut().put(frame) },
            WEIGHT => unsafe { WEIGHT_RECV_BUF.as_mut().put(frame) },
            HEAT => unsafe { HEAT_RECV_BUF.as_mut().put(frame) },
            DOOR => unsafe { DOOR_RECV_BUF.as_mut().put(frame) },
            IPC => unsafe { IPC_RECV_BUF.as_mut().put(frame) },
            _ => continue,
        };

        let pkg = match ret {
            Ok(Some(pkg)) => pkg,
            Ok(None) => continue,
            Err(e) => {
                log!("can recv src:{}, dst:{}, err:{:?}", frame.src(), frame.dst(), e);
                continue;
            }
        };

        #[cfg(feature = "cp_drop")]
        if frame.dst() == IPC {
            super::driver::cmd_uart::write(pkg);
            continue;
        }

        let msg = unsafe { MessageRef::unchecked_new(pkg) };
        match src {
            IPC => dispath(msg),
            // #[cfg(feature = "cp_heat")]
            // WEIGHT => super::weight::dispath(msg),
            _ => continue,
        }
    }
}
